package com.hwd.gmall.gateway.filter;

import com.hwd.gmall.gateway.config.GatewayConfig;
import com.hwd.gmall.gateway.util.IpUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
 * 商城项目网关的全局过滤器
 *
 * @author 黄伟东/Victor
 * @date 2022/5/5 0:28
 */
@Component
public class GmallFilter implements GlobalFilter, Ordered {

    /** token */
    public static final String TOKEN = "token";

    /** 授权的jwt令牌 */
    public static final String AUTHORIZATION = "Authorization";

    /** AUTHORIZATION的前缀 */
    public static final String AUTHORIZATION_PREFIX = "bearer ";

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private GatewayConfig gatewayConfig;

    /**
     * 过滤器的自定义逻辑
     *
     * @param exchange 当前服务的交互对象
     * @param chain    网关的过滤器链对象
     * @return reactor.core.publisher.Mono<java.lang.Void>
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取请求体，响应体
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String requestUrl = request.getURI().getPath();
        if (isIgnoreUrl(requestUrl)) {
            return chain.filter(exchange);
        }

        // 获取请求中的token
        String token = getToken(request);

        // 判断token是否为空，是否与redis中存储的token一致
        if (checkToken(token, request)) {

            // 一致，在网关把请求发送给微服务之前,在request的请求头中完善令牌的格式
            request.mutate().header(AUTHORIZATION, AUTHORIZATION_PREFIX + token);

            // 若有token则放行
            return chain.filter(exchange);

        } else {

            // token为空，或者token被盗用，设置http状态码，401:未经授权
            response.setStatusCode(HttpStatus.UNAUTHORIZED);

            return response.setComplete();
        }
    }

    /**
     * 判断当前请求的url是否为忽略url
     *
     * @param requestUrl 当前请求的url
     * @return boolean
     */
    private boolean isIgnoreUrl(String requestUrl) {
        return gatewayConfig.getIgnoreUrl().stream()
                .anyMatch(url -> url.contains(requestUrl));
    }

    /**
     * 检验token与redis中的token是否一致
     *
     * @param token   token
     * @param request 请求对象
     * @return boolean true：一致 false：不一致
     */
    private boolean checkToken(String token, ServerHttpRequest request) {

        // 判断token是否为空
        if (StringUtils.isEmpty(token)) {
            return false;
        }

        // 获取当前用户的ip地址
        String ipAddress = IpUtil.getGatewayIpAddress(request);

        // 根据ip地址获取保存在redis中token
        String redisToken = stringRedisTemplate.opsForValue().get(ipAddress);

        return token.equals(redisToken);
    }

    /**
     * 获取请求中的token
     *
     * @param request 请求体
     * @return java.lang.String
     */
    private String getToken(ServerHttpRequest request) {

        // 获取用户url中的请求体参数是否有token
        String token = request.getQueryParams().getFirst(TOKEN);
        if (!StringUtils.isEmpty(token)) {
            return token;
        }

        // 若请求体中没有，从head中获取
        token = request.getHeaders().getFirst(TOKEN);
        if (!StringUtils.isEmpty(token)) {
            return token;
        }

        // 若head中也没有，从cookie中获取
        HttpCookie httpCookie = request.getCookies().getFirst(TOKEN);
        if (httpCookie != null) {
            token = httpCookie.getValue();
        }

        return token;
    }

    /**
     * 过滤器执行的信息
     *
     * @return int
     */
    @Override
    public int getOrder() {
        return 0;
    }

}
